package com.jie.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jie.constants.CommonConst;
import com.jie.constants.RedisConstants;
import com.jie.dto.RunTrackDTO;
import com.jie.dto.SportTrackDTO;
import com.jie.dto.ThematicCategoryDTO;
import com.jie.entity.RunTrack;
import com.jie.entity.UserTrack;
import com.jie.enums.MaxPerpsonEnum;
import com.jie.enums.RunStatusEnum;
import com.jie.exception.BizException;
import com.jie.mapper.RunTrackMapper;
import com.jie.mapper.UserTrackMapper;
import com.jie.mongodb.entity.SportTrack;
import com.jie.mongodb.service.impl.RecordServiceImpl;
import com.jie.service.IRunTrackService;
import com.jie.utils.BeanCopyUtils;
import com.jie.utils.RoomIDUtils;
import com.jie.vo.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

import static com.jie.constants.CommonConst.FALSE;
import static com.jie.enums.ZoneEnum.SHANGHAI;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author jie
 * @since 2024-07-10
 */
@Service

public class RunTrackServiceImpl extends ServiceImpl<RunTrackMapper, RunTrack> implements IRunTrackService {
    @Autowired
    private UserTrackMapper userTrackMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private Executor taskExecutor;
    @Autowired
    private RecordServiceImpl recordService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean addSportInfo(SportInfoVO sportInfoVO) {
        //还需要判断当前用户是否有未结束的运动记录
        HashOperations hashOperations = redisTemplate.opsForHash();
        if (hashOperations.hasKey(RedisConstants.USER_SPORT_TRACK_ONLINE_KEY,String.valueOf(sportInfoVO.getUserId()))){
            throw new BizException("请关闭上一个运动记录");
        }
        // 将跑步信息保存在 MySQL 中，并且关联对应的中间表
        RunTrack runTrack = BeanCopyUtils.copyObject(sportInfoVO, RunTrack.class);
        runTrack.setRunStartTime(LocalDateTime.now(ZoneId.of(SHANGHAI.getZone())));
        this.save(runTrack);
        // 确保 runTrack 的 ID 已经被正确设置
        if (runTrack.getId() == null) {
            throw new BizException("保存跑步信息失败，未能获取到跑步信息的 ID");
        }
        // 这里需要添加到中间表去
        UserTrack userTrack = BeanCopyUtils.copyObject(sportInfoVO, UserTrack.class);
        userTrack.setTrackId(runTrack.getId());
        setUserTrackDefaults(userTrack);
        userTrackMapper.insert(userTrack);
        hashOperations.put(RedisConstants.USER_SPORT_TRACK_ONLINE_KEY,String.valueOf(sportInfoVO.getUserId()),runTrack.getId());
        return true;
    }

    /**
     * 关闭单人运动记录的业务方法
     *
     * @param runTrackId 运动轨迹 ID
     * @param userId 用户 ID
     * @return 是否成功关闭运动记录
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean closeSport(Integer runTrackId, Integer userId) {
        //异步获取mongodb数据
        CompletableFuture<SportTrack> latestTrackFuture = CompletableFuture.supplyAsync(() -> recordService.getLatestTrackByTrackIdAndUserId(runTrackId, userId),taskExecutor);
        //获取当前跑步信息
        RunTrack runTrackInfo = this.getById(runTrackId);
        if (Objects.isNull(runTrackInfo)){
            throw new BizException("当前运动记录不存在");
        }
        if (runTrackInfo.getRunStatus().equals(CommonConst.TRUE)){
            throw new BizException("当前运动记录已关闭");
        }
        runTrackInfo.setRunStatus(CommonConst.TRUE);
        runTrackInfo.setRunEndTime(LocalDateTime.now(ZoneId.of(SHANGHAI.getZone())));
        this.updateById(runTrackInfo);
        //直接更新中间表的数据，将状态置为关闭并且记录总距离和总时间
        //更新数据库值
        UserTrack userTrack = userTrackMapper.selectOne(new LambdaQueryWrapper<UserTrack>().eq(UserTrack::getTrackId, runTrackId)
                .eq(UserTrack::getUserId, userId));
        userTrack.setEndDate(LocalDateTime.now(ZoneId.of(SHANGHAI.getZone())));
        // 等待异步获取结果
        SportTrack latestTrackByTrackIdAndUserInfo = latestTrackFuture.join();
        //如果没有数据就将之前的数据填进去做默认
        if (Objects.isNull(latestTrackByTrackIdAndUserInfo)){
            //直接将用户开始的经纬度什么的当全部
            userTrack.setEndLatitude(userTrack.getStartLatitude());
            userTrack.setEndLongitude(userTrack.getStartLongitude());
            userTrack.setRunDate(userTrack.getRunDate());
            userTrack.setRunDistance(userTrack.getRunDistance());
            //关闭
            userTrack.setTrackStatus(CommonConst.TRUE);
            userTrackMapper.updateById(userTrack);
            redisTemplate.opsForHash().delete(RedisConstants.USER_SPORT_TRACK_ONLINE_KEY,String.valueOf(userId));
            return true;
        }
        List<BigDecimal> de = latestTrackByTrackIdAndUserInfo.getLongitudeAndLatitudeList().get(latestTrackByTrackIdAndUserInfo.getLongitudeAndLatitudeList().size() - 1);
        //更新中间表数据 这个是因为经纬度是按匹配的两个字段来的
        userTrack.setEndLatitude(de.get(0));
        userTrack.setEndLongitude(de.get(1));
        userTrack.setRunDate(latestTrackByTrackIdAndUserInfo.getTotalTime());
        userTrack.setRunDistance(latestTrackByTrackIdAndUserInfo.getTotalDistance());
        //关闭
        userTrack.setTrackStatus(CommonConst.TRUE);
        userTrackMapper.updateById(userTrack);

        redisTemplate.opsForHash().delete(RedisConstants.USER_SPORT_TRACK_ONLINE_KEY,String.valueOf(userId));
        return true;
    }
    /***
     * 收集运动轨迹信息 插入到mongodb
     * @param trackInfoVO
     */
    @Override
    @Async("taskExecutor")
    public void collectLocationInfo(TrackInfoVO trackInfoVO) {
        trackInfoVO.setRecordTime(LocalDateTime.now(ZoneId.of(SHANGHAI.getZone())));
        //将数据插入到mongodb
        SportTrack sportTrack = BeanCopyUtils.copyObject(trackInfoVO, SportTrack.class);
        mongoTemplate.insert(sportTrack);
    }

    /**
     * 分页获取用户轨迹信息
     * @param runTrackId 轨迹id
     * @param userId 用户id
     * @param conditionVO 分页信息
     * @return
     */
    @Override
    public PageResult<SportTrackDTO> getTrackInfoPage(Integer runTrackId, Integer userId, ConditionVO conditionVO) {
        Pageable pageable = PageRequest.of(Math.toIntExact(conditionVO.getCurrent()), Math.toIntExact(conditionVO.getSize()));
        Page<SportTrack> tracksByTrackIdAndUserIdOrderByRecordTimeAscWithPaging = recordService.getTracksByTrackIdAndUserIdOrderByRecordTimeAscWithPaging(runTrackId, userId, pageable);
        List<SportTrackDTO> sportTrackDTOList = BeanCopyUtils.copyList(tracksByTrackIdAndUserIdOrderByRecordTimeAscWithPaging.getContent(), SportTrackDTO.class);
        return new PageResult<>(sportTrackDTOList, sportTrackDTOList.size());
    }

    //-----------------------------------------------------------------组队逻辑

    /**
     * 生成跑步轨迹房间信息
     * @param userRunTrackVO
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public RunTrackDTO startMatchInfo(UserRunTrackVO userRunTrackVO) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        //这里可以做一下判断是否之前有开的房间未关闭但一般来说如果房主是不能重复点击两次建房的
        // 将跑步信息保存在 MySQL 中，并且关联对应的中间表
        RunTrack runTrack = BeanCopyUtils.copyObject(userRunTrackVO, RunTrack.class);
        // 改为开启状态
        runTrack.setRunStatus(RunStatusEnum.RUNNING.getCode());
        //设置最大人数
        runTrack.setRoomPeople(MaxPerpsonEnum.MAX_PERPSON.getCode());
        // 设置开始时间
        runTrack.setRunStartTime(LocalDateTime.now(ZoneId.of(SHANGHAI.getZone())));
        this.save(runTrack);
        // 确保 runTrack 的 ID 已经被正确设置
        if (runTrack.getId() == null) {
            throw new BizException("保存跑步信息失败，未能获取到跑步信息的 ID");
        }
        // 这里需要添加到中间表去
        UserTrack userTrack = BeanCopyUtils.copyObject(userRunTrackVO, UserTrack.class);
        userTrack.setTrackId(runTrack.getId());
        setUserTrackDefaults(userTrack);
        userTrackMapper.insert(userTrack);
        RunTrackDTO runTrackDTO = BeanCopyUtils.copyObject(runTrack, RunTrackDTO.class);
        //将房间信息放入redis，对于一些房间与用户对应的在线关系，逻辑在netty的连接的时候通过心跳那块进行关联
        //类似于 key=roomId:userId value=当前netty服务器编码 然后设置心跳时间作为在线区分
        hashOperations.put(RedisConstants.RUNNING_TRACK_INFO_KEY,userRunTrackVO.getRoomId(),runTrackDTO);
        //这里还需要将自己信息加入对应的channel去
        return runTrackDTO;
    }

    /**
     * 加入房间逻辑
     * @param sportInfoVO
     * @return
     */
    @Override
    public boolean joinMatchRoom(SportInfoVO sportInfoVO) {
        //对于多人房间的加入应该是这里简单判断房间号是否开启即可
        //并加用户信息都添加到中间表去，然后通过发送一条websocket的房间加入信息 加入对应的channel
        return false;
    }

    /**
     * 更新当前房间所有人的跑步记录 这个方法应该交给mq去执行
     * @param runTrackId
     */
    public void updateUsersTrackInfo(Integer runTrackId) {
        //异步获取mongodb的数据
        CompletableFuture<List<SportTrack>> listCompletableFuture = CompletableFuture.supplyAsync(() -> recordService.getLatestRecordsByTrackIdAndUserIdGroupedByUsername(runTrackId), taskExecutor);
        //批量更新状态 查询每个人的跑步记录
        List<UserTrack> userTracks = userTrackMapper.selectList(new LambdaQueryWrapper<UserTrack>()
                .eq(UserTrack::getTrackId, runTrackId)
                //查询状态值为退出的用户
                .eq(UserTrack::getTrackStatus, FALSE)
                .orderByAsc()
        );
        //这里从mongodb查询到各个人最后一条信息的经纬度与时长，距离集合 直接用异步做。
        List<SportTrack> latestRecordsByTrackIdAndUsernameGroupedByUsername = listCompletableFuture.join();
        for (SportTrack sportTrack : latestRecordsByTrackIdAndUsernameGroupedByUsername) {
            //获取到每个人的sportTrack
            //修改各个人的总时长，总距离，结束经纬度 然后批量插入
            for (UserTrack userTrack : userTracks) {
                if (sportTrack.getUserId().equals(userTrack.getUserId())){
                    //TODO 未完成批量修改逻辑
                }
            }
        }
    }

    /**
     * 设置默认值
     * @param userTrack
     */
    private void setUserTrackDefaults(UserTrack userTrack) {
        userTrack.setConsumable(0);
        userTrack.setRunDate(0);
        userTrack.setRunDistance(0.0);
    }

}
